#encoding: utf-8
'''
Created on 2012-4-10

@author: huangjacky
@copyright: TeamV - Group: WhutOJ
工作线程模块
'''

from threading import Thread
import pyojConst
from subprocess import Popen, PIPE
from platform import system
from pyojData import PyOJData
from pyojResponse import PyOJResponse
import httplib, urllib
import os
import resource
import signal

K = 1024
M = 1024 * K
G = 1024 * M

class PyOJExecutorThread(Thread):
    '''
    用来执行编译器和子进程的线程
    '''
    
    def __init__(self, request):
        # 调用父类的构造方法
        Thread.__init__(self) 
        #super(PyOJExecutorThread,self).__init()
        self.__data = PyOJData(orequest=request)
        self.__data.response = PyOJResponse()
        # 判断当前的系统
        sysstr = system()
        if sysstr == 'Windows':
            self.execdir = 'c:\\%s'
        elif sysstr == 'Linux':
            self.execdir = '/home/huangjacky/%s'
        else:
            self.execdir = ''
    
    #@summary: 本函数主要对对应的命令进行执行
    #@param cmd: 执行的编译命令 
    #@return: 编译成功返回True，编译过程失败返回False
    def ojcompile(self, cmd):
        ps = Popen(cmd, shell=True, universal_newlines=True, close_fds=True, stdout=PIPE, stderr=PIPE)
        (cOut, cErr) = ps.communicate()
        # ps.returncode 的意义: 编译成功 0
        #                                        编译失败 1
        sInfo = (cOut + cErr).strip()
        if ps.returncode == 0: # 执行成功，判断是否有warnning
            if sInfo.find("警告：") > -1:
                self.__data.response.flag = pyojConst.SUCCESS_COMPILE_WARNNING
                self.__data.response.info = sInfo
        else: # 编译错误
            self.__data.response.flag = pyojConst.ERROR_COMPILE
            self.__data.response.info = sInfo
        return not self.__data.response.flag > pyojConst.ERROR_BASE 
    
    #@summary: 将运行的结果发送到web端
    #@return: 无
    def ojsend(self):
        # 回传的数据, 暂时没有起到应用的效果。
#        params = urllib.urlencode({'uid': self.data.request.uid, \
#                                   'qid': self.data.request.qid, \
#                                   'lid': self.data.request.lid, \
#                                   'runtime': self.__data.response.runtime, \
#                                   'runmem': self.__data.response.runmem, \
#                                   'runoutput': self.__data.response.runoutput, \
#                                   'flag': self.__data.response.flag, \
#                                   'info': self.__data.response.info})
#        headers = {'Accept':'text/html', \
#                   'Content-Type':'application/x-www-form-urlencoded', \
#                   'User-Agent':'WhutOJ-Daemon'}
#        # 主机地址 和 接收的页面
#        host = '127.0.0.1'
#        uripath = 'testdaemon/public/index/feedback'
#        conn = httplib.HTTPConnection(host)
#        conn.request('POST', uripath, params, headers)
#        # 查看主机的响应
#        response = conn.getresponse()
#        # 判断主机的响应代码,暂时没有实现
#        if response.status == 200:
#            print "response ok;"
#        # 关闭连接
#        conn.close()
        
        print  "状态码：%8x\r\n信息：%s" % (self.data.response.flag, self.data.response.info)
    
    #@summary: 运行子程序
    #@param childpath: 编译程序生成的子程序的路径
    #@return: 无
    def ojrunchild(self, childpath):
        # 如果执行编译成功了，那么执行子程序, 计算开始时间
        pid = os.fork()
        if pid < 0 : # fork 失败
            self.data.response.flag = pyojConst.ERROR_FORK
        elif pid == 0: #子进程
            _, tmp = resource.getrlimit(resource.RLIMIT_CPU)
            #@todo: 需要检测setrlimit设置是否成功
            resource.setrlimit(resource.RLIMIT_CPU, (self.data.request.limitedtime, tmp))  # 设置时间限制，单位：秒
            (_, tmp) = resource.getrlimit(resource.RLIMIT_STACK)
            resource.setrlimit(resource.RLIMIT_STACK, (self.data.request.limitedmem, tmp)) # 设置内存限制，单位：字节
            # 设置程序的运行目录和用户权限，默认设置成最小的权限。
            os.chroot(self.execdir)
            os.setuid(99)
            os.setgid(99)
            # 设置完成开始运行子进程 
            ps = Popen(childpath, shell=True, universal_newlines=True, close_fds=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
            self.data.response.pid = ps.pid
            # 输入参数,替换一下
            sInput = self.data.request.standardinput.replace(',', '\n') + '\n'
            (self.data.response.runoutput, cerr) = ps.communicate(sInput)
            if os.WIFEXITED(ps.returncode): # 子程序正确结束，判断输出是否正确
                if cmp(self.data.response.runoutput, self.data.request.standardoutput) != 0: # 结果不正确的话
                    self.data.response.flag = pyojConst.ERROR_RUN_CHILD_OUTPUT
                    self.data.response.info += "\r\n输出结果不正确"
            else: # 子程序非正常结束
                signal = os.WTERMSIG(ps.returncode) # 信号量
                if signal == signal.SIGXCPU : # cpu 时间超出限制
                    self.data.response.flag = pyojConst.ERROR_CPU_EXECEED
                    self.data.response.info += "\r\n%s" % cerr
                else:
                    self.data.response.flag = pyojConst.ERROR_MEM_EXECEED
                    self.data.response.info += "\r\nStack limited execeeded!"
        else: # 父进程
            (_, status) = os.waitpid(pid, 0) # 等待子进程运行完毕
            # 获取程序的运行时间 和 运行内存
            if status != 0:
                self.data.response.flag = pyojConst.ERROR_UNDEFINED
            else:
                usage = resource.getrusage(resource.RUSAGE_CHILDREN)
                self.data.response.runtime = usage.ru_utime + usage.ru_stime
                # 所消耗的内存暂时无法计算，所以这里没有计算
                self.data.response.runmem = 0
                
        
    def run(self):# 覆盖掉执行方法
        sOutput = "%d_%d" % (self.data.request.uid, self.data.request.qid)
        child = self.execdir % sOutput
        # 完整的编译命令
        #@todo: 对self.data.request.lid来判断具体是某一个语言然后执行相应的命令，这里暂时只考虑c语言的
        sCmd = self.data.request.cmdline % child
        flag = self.ojcompile(sCmd) # 编译子程序
        try:
            if flag:
                self.ojrunchild(child)
        finally:
            self.ojsend()
    
    # 属性字段
    def get_data(self):
        return self.__data

    def set_data(self, value):
        self.__data = value

    def del_data(self):
        del self.__data

    data = property(get_data, set_data, del_data, "整个执行过程中的数据")
        
        
    
